﻿#region File and License Information
/*
<File>
	<License>
		Copyright © 2009 - 2017, Daniel Vaughan. All rights reserved.
		This file is part of Calcium (http://calciumsdk.net), 
		which is released under the MIT License.
		See file /Documentation/License.txt for details.
	</License>
	<CreationDate>2011-12-15 15:27:25Z</CreationDate>
</File>
*/
#endregion

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;

namespace Outcoder.UI.Xaml
{
	public class UICommand<TParameter> : DelegateCommand<TParameter>, IUICommand
	{
		public string Id { get; set; }

		/// <summary>
		/// Creates a UICommand that will perform its canExecute evaluation 
		/// using the async model.
		/// </summary>
		/// <param name="id">An arbitrary identifier that is used 
		/// to identify commands in your application during execution. 
		/// Can be <c>null</c>.</param>
		/// <param name="executeAction">The action that is performed 
		/// when the execute method is called. This is a required parameter.</param>
		/// <param name="canExecuteFunc">Can be <c>null</c>. A method that is used to determine 
		/// if this command can be executed. 
		/// Ordinarily this will set the visual state of a button, for example, to be disabled.
		/// If <c>null</c> the command is set to always executable.</param>
		public UICommand(
			string id,
			Func<TParameter, Task> executeAction,
			Func<TParameter, Task<bool>> canExecuteFunc = null, 
			IExceptionHandler exceptionHandler = null,
			[CallerFilePath] string filePath = null,
			[CallerLineNumber] int lineNumber = 0)
			: base(executeAction, false, canExecuteFunc, exceptionHandler, filePath, lineNumber)
		{
			Id = id;
		}

		/// <summary>
		/// Creates a UICommand that will perform its canExecute evaluation 
		/// using the non-async model present in the <see cref="DelegateCommand"/>.
		/// </summary>
		public UICommand(
			Action<TParameter> executeAction,
			Func<TParameter, bool> canExecuteFunc = null, 
			string id = null,
			IExceptionHandler exceptionHandler = null,
			[CallerFilePath] string filePath = null,
			[CallerLineNumber] int lineNumber = 0)
			: base(executeAction, canExecuteFunc, exceptionHandler, filePath, lineNumber)
		{
			Id = id;
		}
		
		string text;

		/// <summary>
		/// The title of this command that may be displayed to the user.
		/// </summary>
		public string Text
		{
			get
			{
				return text;
			}
			set
			{
				AssignPropertyValue(ref text, value, false);
			}
		}

		Func<TParameter, Task<string>> iconUrlFunc;

		/// <summary>
		/// A func to retrieve the icon URL of the command.
		/// </summary>
		public Func<TParameter, Task<string>> IconUrlFunc
		{
			get
			{
				return iconUrlFunc;
			}
			set
			{
				AssignPropertyValue(ref iconUrlFunc, value, true);
			}
		}

		string iconUrl;

		public string IconUrl
		{
			get
			{
				return iconUrl;
			}
			set
			{
				AssignPropertyValue(ref iconUrl, value, false);
			}
		}

		async Task RefreshIconUrlAsync(TParameter parameter)
		{
			if (iconUrlFunc != null)
			{
				IconUrl = await iconUrlFunc(parameter);
			}
		}

		#region Icon Font

		Func<TParameter, Task<string>> iconFontFunc;

		/// <summary>
		/// A func to retrieve the icon URL of the command.
		/// </summary>
		public Func<TParameter, Task<string>> IconFontFunc
		{
			get
			{
				return iconFontFunc;
			}
			set
			{
				AssignPropertyValue(ref iconFontFunc, value, true);
			}
		}

		string iconFont;

		public string IconFont
		{
			get
			{
				return iconFont;
			}
			set
			{
				AssignPropertyValue(ref iconFont, value, false);
			}
		}

		async Task RefreshIconFontAsync(TParameter parameter)
		{
			if (iconFontFunc != null)
			{
				IconFont = await iconFontFunc(parameter);
			}
		}

		#endregion

		Func<TParameter, Task<string>> textFunc;

		/// <summary>
		/// A func to retrieve the text of the command.
		/// </summary>
		public Func<TParameter, Task<string>> TextFunc
		{
			get
			{
				return textFunc;
			}
			set
			{
				AssignPropertyValue(ref textFunc, value, true);
			}
		}

		Func<TParameter, Task<bool>> isVisibleFunc;

		public Func<TParameter, Task<bool>> IsVisibleFunc
		{
			get
			{
				return isVisibleFunc;
			}
			set
			{
				AssignPropertyValue(ref isVisibleFunc, value, true);
			}
		}

		bool visible = true;

		public bool Visible
		{
			get
			{
				return visible;
			}
			set
			{
				AssignPropertyValue(ref visible, value, false);
			}
		}

		#region IsChecked
		Func<TParameter, Task<bool>> isCheckedFunc;

		public Func<TParameter, Task<bool>> IsCheckedFunc
		{
			get
			{
				return isCheckedFunc;
			}
			set
			{
				AssignPropertyValue(ref isCheckedFunc, value, true);
			}
		}

		async Task RefreshIsCheckedAsync(TParameter parameter)
		{
			if (isCheckedFunc != null)
			{
				IsChecked = await isCheckedFunc(parameter);
			}
		}

		bool isChecked;

		public bool IsChecked
		{
			get
			{
				return isChecked;
			}
			set
			{
				AssignPropertyValue(ref isChecked, value, false);
			}
		}
		#endregion

		#region IconCharacter
		Func<TParameter, Task<string>> iconCharacterFunc;

		public Func<TParameter, Task<string>> IconCharacterFunc
		{
			get
			{
				return iconCharacterFunc;
			}
			set
			{
				AssignPropertyValue(ref iconCharacterFunc, value, true);
			}
		}

		async Task RefreshIconCharacterAsync(TParameter parameter)
		{
			if (iconCharacterFunc != null)
			{
				IconCharacter = await iconCharacterFunc(parameter);
			}
		}

		string iconCharacter;

		public string IconCharacter
		{
			get
			{
				return iconCharacter;
			}
			set
			{
				AssignPropertyValue(ref iconCharacter, value, false);
			}
		}
		#endregion

		public override bool CanExecute(TParameter parameter)
		{
			bool result = base.CanExecute(parameter);

			RefreshUIProperties(parameter);

			return result;
		}

		public override async Task<bool> CanExecuteAsync(TParameter parameter)
		{
			bool result = await base.CanExecuteAsync(parameter);

			await RefreshUIPropertiesAsync(parameter);

			return result;
		}

		async void RefreshUIProperties(TParameter parameter)
		{
			try
			{
				await RefreshUIPropertiesAsync(parameter);
			}
			catch (Exception ex)
			{
				if (!HandleException(ex, this))
				{
					throw;
				}
			}
		}

		protected virtual async Task RefreshUIPropertiesAsync(TParameter parameter)
		{
			parameter = ProcessParameter(parameter);

			await RefreshVisibilityAsync(parameter);
			await RefreshTextAsync(parameter);
			await RefreshIconUrlAsync(parameter);
			await RefreshIsCheckedAsync(parameter);
			await RefreshIconCharacterAsync(parameter);
			await RefreshIconFontAsync(parameter);
		}

		async Task RefreshVisibilityAsync(TParameter parameter = default(TParameter))
		{
			parameter = ProcessParameter(parameter);

			if (isVisibleFunc != null)
			{
				Visible = await isVisibleFunc(parameter);
			}
		}

		async Task RefreshTextAsync(TParameter parameter = default(TParameter))
		{
			parameter = ProcessParameter(parameter);

			if (textFunc != null)
			{
				Text = await textFunc(parameter);
			}
		}

		public override async Task RefreshAsync(object commandParameter = null)
		{
			TParameter parameter = ProcessParameter(commandParameter);
			await base.RefreshAsync(parameter);
			await RefreshUIPropertiesAsync(parameter);
		}

	}

	public class UICommand : UICommand<object>
	{
		public UICommand(
			Action<object> executeAction, 
			Func<object, bool> canExecuteFunc = null,
			string id = null,
			IExceptionHandler exceptionHandler = null,
			[CallerFilePath] string filePath = null,
			[CallerLineNumber] int lineNumber = 0)
			: base(executeAction, canExecuteFunc, id,
					exceptionHandler, filePath, lineNumber)
		{
		}

		public UICommand(
			string id,
			Func<object, Task> executeAction,
			Func<object, Task<bool>> canExecuteFunc = null,
			IExceptionHandler exceptionHandler = null,
			[CallerFilePath] string filePath = null,
			[CallerLineNumber] int lineNumber = 0)
			: base(id, executeAction, canExecuteFunc,
					exceptionHandler, filePath, lineNumber)
		{
		}
	}

	public class UICompositeCommand : IUICommand, INotifyPropertyChanged
	{
		IUICommand selectedCommand;
		readonly List<IUICommand> commands = new List<IUICommand>();
		
		public List<IUICommand> Commands => commands;

		public string Id { get; set; }

		readonly Dictionary<IUICommand, object> commandParameters 
			= new Dictionary<IUICommand, object>();

		public void SetCommandParameter(IUICommand command, object parameter)
		{
			if (parameter != null)
			{
				commandParameters[command] = parameter;
			}
			else
			{
				commandParameters.Remove(command);
			}
		}

		public async Task RefreshAsync(object commandParameter = null)
		{
			foreach (IUICommand uiCommand in commands)
			{
				object parameter;
				commandParameters.TryGetValue(uiCommand, out parameter);

				await uiCommand.RefreshAsync(parameter ?? commandParameter);
			}
		}

		public IUICommand SelectedCommand
		{
			get
			{
				return selectedCommand;
			}
			set
			{
				ArgumentValidator.AssertNotNull(value, "value");

				if (value == selectedCommand)
				{
					return;
				}

				if (!commands.Contains(value))
				{
					throw new ArgumentOutOfRangeException("Command is not contained within the collection.");
				}

				if (selectedCommand != null)
				{
					StopMonitoringCommand(selectedCommand);
				}
				selectedCommand = value;
				MonitorCommand(selectedCommand);

				OnPropertyChanged(string.Empty);
			}
		}

		public int SelectedCommandIndex
		{
			get
			{
				if (selectedCommand == null)
				{
					return -1;
				}

				return commands.IndexOf(selectedCommand);
			}
			set
			{
				if (value < 0 || value > commands.Count - 1)
				{
					throw new ArgumentOutOfRangeException();
				}

				SelectedCommand = commands[value];
			}
		}

		void MonitorCommand(IUICommand uiCommand)
		{
			var inpc = uiCommand as INotifyPropertyChanged;
			if (inpc == null)
			{
				return;
			}

			inpc.PropertyChanged -= HandleSelectedCommandPropertyChanged;
			inpc.PropertyChanged += HandleSelectedCommandPropertyChanged;
		}

		void StopMonitoringCommand(IUICommand uiCommand)
		{
			var inpc = uiCommand as INotifyPropertyChanged;
			if (inpc == null)
			{
				return;
			}

			inpc.PropertyChanged -= HandleSelectedCommandPropertyChanged;
		}

		void HandleSelectedCommandPropertyChanged(object sender, PropertyChangedEventArgs e)
		{
			// ReSharper disable once ExplicitCallerInfoArgument
			OnPropertyChanged(e.PropertyName);
		}

		public UICompositeCommand(params IUICommand[] commands)
		{
			this.commands.AddRange(commands);

			if (commands.Any())
			{
				SelectedCommandIndex = 0;
			}
		}

		public bool CanExecuteDefaultValue { get; set; }

		public bool CanExecute(object parameter)
		{
			if (selectedCommand != null)
			{
				return selectedCommand.CanExecute(parameter);
			}

			return CanExecuteDefaultValue;
		}

		public void Execute(object parameter)
		{
			selectedCommand?.Execute(parameter);
		}

		public event EventHandler CanExecuteChanged;

		public string TextDefaultValue { get; set; }

		public string Text
		{
			get
			{
				if (selectedCommand != null)
				{
					return selectedCommand.Text;
				}

				return TextDefaultValue;
			}
		}

		bool visibleDefaultValue = true;

		public bool VisibleDefaultValue
		{
			get
			{
				return visibleDefaultValue;
			}
			set
			{
				visibleDefaultValue = value;
			}
		}

		public bool Visible
		{
			get
			{
				if (selectedCommand != null)
				{
					return selectedCommand.Visible;
				}

				return visibleDefaultValue;
			}
		}
		
		public bool Enabled
		{
			get
			{
				if (selectedCommand != null)
				{
					return selectedCommand.Enabled;
				}

				return true;
			}
		}

		public string IconUrl => selectedCommand?.IconUrl;

		public string IconCharacter => selectedCommand?.IconCharacter;

		public string IconFont => selectedCommand?.IconFont;

		public bool IsChecked
		{
			get
			{
				if (selectedCommand != null)
				{
					return selectedCommand.IsChecked;
				}

				return false;
			}
		}

		public event PropertyChangedEventHandler PropertyChanged;

		protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
		{
			PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
		}
	}
}
